Implement as langchain codeinterpreter sandbox provider#318
Conversation
Signed-off-by: zhoujinyu <2319109590@qq.com>
Signed-off-by: zhoujinyu <2319109590@qq.com>
Signed-off-by: zhoujinyu <2319109590@qq.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a LangChain integration for AgentCube, implementing a BaseSandbox backend for Deep Agents along with supporting examples and E2E tests. The core SDK is updated with a new execute_command_result method to handle command execution without raising exceptions on non-zero exit codes. Review feedback suggests avoiding hardcoded model versions in examples, refining output formatting when stdout is empty, ensuring robust handling of potential null values for exit codes, and maintaining consistent type hints across the SDK.
| from langchain_anthropic import ChatAnthropic | ||
|
|
||
| return ChatAnthropic( | ||
| model=os.environ.get("ANTHROPIC_MODEL", "claude-haiku-4-5-20251001"), |
There was a problem hiding this comment.
Hardcoding a specific dated model version like claude-haiku-4-5-20251001 as a default can lead to maintenance issues when the model is deprecated or updated. Consider using a more stable alias (e.g., claude-3-5-haiku-latest) or requiring the user to provide the model via environment variables without a hardcoded default.
| out = r.get("stdout") or "" | ||
| stderr = (r.get("stderr") or "").strip() | ||
| if stderr: | ||
| out += f"\n<stderr>{stderr}</stderr>" |
There was a problem hiding this comment.
Appending stderr with a leading newline can result in unexpected output formatting if stdout is empty (the result will start with a newline). Consider checking if out is empty before adding the newline.
| out += f"\n<stderr>{stderr}</stderr>" | |
| out = f"{out}\n<stderr>{stderr}</stderr>" if out else f"<stderr>{stderr}</stderr>" |
| return { | ||
| "stdout": result.get("stdout") or "", | ||
| "stderr": result.get("stderr") or "", | ||
| "exit_code": int(result.get("exit_code", -1)), |
There was a problem hiding this comment.
The use of result.get("exit_code", -1) will return None if the key "exit_code" exists in the response but its value is null. Passing None to int() will raise a TypeError. It is safer to handle the None case explicitly to avoid potential crashes if the API returns a null exit code.
| "exit_code": int(result.get("exit_code", -1)), | |
| "exit_code": int(result.get("exit_code") if result.get("exit_code") is not None else -1), |
| return self.dp_client.execute_command(command, timeout) | ||
|
|
||
| def execute_command_result( | ||
| self, command: str, timeout: Optional[float] = None |
There was a problem hiding this comment.
The type hint for command should be updated to str | list[str] to be consistent with the underlying CodeInterpreterDataPlaneClient.execute_command_result method which accepts both types. This ensures better type safety and documentation for users of the SDK.
| self, command: str, timeout: Optional[float] = None | |
| self, command: str | list[str], timeout: Optional[float] = None |
There was a problem hiding this comment.
Pull request overview
This PR adds a LangChain Deep Agents sandbox backend for AgentCube’s Code Interpreter by introducing a new langchain-agentcube integration package and extending the Python SDK with a non-raising command execution API that the sandbox can use.
Changes:
- Added
langchain-agentcubeintegration implementingAgentcubeSandbox(Deep AgentsBaseSandbox) with execute + file upload/download support. - Extended the Python SDK with
execute_command_result()to return{stdout, stderr, exit_code}without raising on non-zero exit codes. - Wired the new integration into E2E runs (new E2E test + install step) and ensured CI sets up Python 3.11.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/e2e/test_langchain_agentcube_sandbox.py | New E2E coverage for the Deep Agents sandbox adapter against live Router/WM. |
| test/e2e/run_e2e.sh | Installs the new integration and runs the new LangChain sandbox E2E test. |
| sdk-python/agentcube/code_interpreter.py | Exposes execute_command_result() on the high-level client. |
| sdk-python/agentcube/clients/code_interpreter_data_plane.py | Implements execute_command_result() and refactors execute_command() to use it. |
| integrations/langchain-agentcube/README.md | Usage docs for the new integration and example script. |
| integrations/langchain-agentcube/pyproject.toml | New Python package metadata/deps for the integration. |
| integrations/langchain-agentcube/langchain_agentcube/sandbox.py | Implements AgentcubeSandbox (execute + upload/download) on top of the SDK client. |
| integrations/langchain-agentcube/langchain_agentcube/init.py | Exports AgentcubeSandbox. |
| integrations/langchain-agentcube/example/deep_agent_sandbox.py | Example of using create_deep_agent(..., backend=AgentcubeSandbox(...)). |
| .github/workflows/e2e.yml | Ensures Python 3.11 is available for E2E runs. |
| verbose=True, | ||
| ) as client: | ||
| backend = AgentcubeSandbox(client=client) | ||
| self.assertTrue(backend.id) |
|
|
||
| def _normalize_remote_path(path: str) -> str: | ||
| """Map paths from Deep Agents (often absolute) to session workspace-relative paths.""" | ||
| return path.replace("\\", "/").strip().lstrip("/") |
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #318 +/- ##
==========================================
+ Coverage 47.57% 49.60% +2.03%
==========================================
Files 30 30
Lines 2819 2885 +66
==========================================
+ Hits 1341 1431 +90
+ Misses 1338 1301 -37
- Partials 140 153 +13
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
record for result
|
|
It's unclear why Deepseek keeps getting hung up on the path issue and insists on using absolute paths (which would definitely fail due to the existence of picod). However, LangChain itself is functioning correctly, and Deepagent can plan normally. |
|
/cc @hzxuzhonghu @acsoto |
Signed-off-by: zhoujinyu <2319109590@qq.com>
What does this mean? While deepagent works well |
There was a problem hiding this comment.
I am thinking about contributing to upstream lang chain organization if possible
There was a problem hiding this comment.
about what. The agentcube Code Interpreter?
There was a problem hiding this comment.
This implement, you can see aws agentcore is there.
|
Ah, you mean it cannot find |
exactly,The model interprets "hello.py in the working directory" as /hello.py (disk root), while the actual file is located in {working directory}/hello.py (e.g., /root/hello.py). |
it creats lots of hello.py in different places |
|
at last it give up creating file in workspace and run |
|
Seems the client want to upload the file in |
|
@FAUST-BENCHOU should we change the logic to write the file to the absolute path specified by client? Or not sure if it can work if we can update the |
make sense.I'll try them to solve the path problem |
|
we may need to overwrite agentcube behavior since deepagent ask for using after replacing the absolute path to relative path it works well without path problem |
| _EXECUTE_PATH_RE = re.compile( | ||
| r"/(?:[a-zA-Z0-9._@+-]+/)*[a-zA-Z0-9._@+-]+" |
| def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]: | ||
| responses: list[FileUploadResponse] = [] | ||
| for path, content in files: | ||
| rel = normalize_remote_path(path) | ||
| if not rel: | ||
| responses.append(FileUploadResponse(path=path, error="invalid_path")) | ||
| continue | ||
| tmp_path: str | None = None | ||
| try: | ||
| fd, tmp_path = tempfile.mkstemp(prefix="agentcube-upload-", suffix=".bin") | ||
| with os.fdopen(fd, "wb") as f: | ||
| f.write(content) | ||
| self._client.upload_file(tmp_path, rel) | ||
| responses.append(FileUploadResponse(path=rel, error=None)) | ||
| except Exception as e: | ||
| responses.append(FileUploadResponse(path=path, error=str(e))) | ||
| finally: | ||
| if tmp_path: | ||
| try: | ||
| os.unlink(tmp_path) | ||
| except OSError: | ||
| pass | ||
| return responses | ||
|
|
||
| def download_files(self, paths: list[str]) -> list[FileDownloadResponse]: | ||
| responses: list[FileDownloadResponse] = [] | ||
| for path in paths: | ||
| rel = normalize_remote_path(path) | ||
| if not rel: | ||
| responses.append( | ||
| FileDownloadResponse(path=path, content=None, error="invalid_path") | ||
| ) | ||
| continue |
| @@ -142,20 +141,32 @@ def execute_command(self, command: Union[str, List[str]], timeout: Optional[floa | |||
| } | |||
| body = json.dumps(payload).encode('utf-8') | |||
|
|
|||
| # Add a buffer to the read timeout to allow PicoD to return the timeout response | |||
| # otherwise requests might raise ReadTimeout before we get the JSON response with exit_code 124 | |||
| read_timeout = timeout_value + 2.0 if isinstance(timeout_value, (int, float)) else timeout_value | |||
|
|
|||
|
Its a littile tricky in fact.So the virtual absolute path of Deep Agents is still displayed in the model; what actually enters picod and shell is the working directory relative path (or the path that can be resolved under cwd).But |
|
Not quite understand, can we talk online |
|
There is an alternative way, on picode side, support absolute path. |
ya i understand ,use --workspace=/ |
Signed-off-by: zhoujinyu <2319109590@qq.com>
7012557 to
fec8a33
Compare
|
it works also well now, just might require changing picod's default behavior. |
|
So i just add a individual code-interpreter yml here |
hzxuzhonghu
left a comment
There was a problem hiding this comment.
Good integration - wiring AgentCube Code Interpreter to LangChain Deep Agents.
integrations/langchain-agentcube/langchain_agentcube/sandbox.py
- Lines 24-25 (
_normalize_remote_path): Stripping leading slashes means Deep Agents absolute paths (like/hello.py) become relative to the workspace root. This is correct ONLY when picod uses--workspace=/. Make sure the README requirement (--workspace=/) is enforced or validated at runtime, otherwise uploads will silently go to the wrong directory. - Lines 59-61 (
execute): Thetimeoutparameter is passed asfloatbut the SDK likely expects seconds as a number - verify the SDK'sexecute_command_resultsignature acceptsfloat. - Lines 65-68: Appending stderr to stdout with
<stderr>tags is a pragmatic choice for the Deep AgentsExecuteResponsewhich only has oneoutputfield. Consider whether the LLM agent can parse these tags reliably. - Lines 73-94 (
upload_files): Temp file cleanup infinallyblock is correct. Good. - Lines 96-117 (
download_files): Theos.close(fd)before passingtmp_pathtodownload_fileis correct - the file descriptor is not needed since download_file opens the path.
integrations/langchain-agentcube/example/deep_agent_sandbox.py
- Line 86:
sys.path.insert(0, ...)to find the sdk-python package is fragile. Consider adding the SDK as a proper dependency in pyproject.toml instead.
integrations/langchain-agentcube/code-interpreter.yaml
- Line 18:
--workspace=/is critical for Deep Agents path alignment. Good that it is documented here.
LGTM with the note about runtime workspace validation.
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: hzxuzhonghu The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
What type of PR is this?
What this PR does / why we need it:
Which issue(s) this PR fixes:
Fixes #278
Special notes for your reviewer:
Does this PR introduce a user-facing change?: